"""BLE notification management for real-time status updates."""

import logging
import json
import time
import threading
from typing import Dict, List, Optional, Any, Set
from dataclasses import dataclass, asdict
from queue import Queue, Empty
from enum import IntEnum

# Try to import syslog, fallback to logging on Windows
try:
    import syslog
    HAS_SYSLOG = True
except ImportError:
    HAS_SYSLOG = False

logger = logging.getLogger(__name__)


class StatusCode(IntEnum):
    """Status codes for WiFi configuration process."""
    
    # Idle state
    IDLE = 0x00
    
    # Scanning states
    SCANNING_START = 0x10
    SCANNING_COMPLETE = 0x11
    
    # Connection states
    CONNECTING = 0x20
    CONNECTED = 0x21
    CONNECTION_FAILED = 0x22
    
    # Authentication states
    AUTH_SUCCESS = 0x30
    AUTH_FAILED = 0x31
    
    # IP configuration states
    IP_OBTAINED = 0x40
    DHCP_FAILED = 0x41
    
    # Network readiness states
    NETWORK_READY = 0x50
    NETWORK_UNREACHABLE = 0x51
    
    # Error states
    SIGNAL_WEAK = 0x60
    TIMEOUT = 0x61
    INVALID_CONFIG = 0x62


# Internationalization messages
MESSAGES = {
    "zh_CN": {
        StatusCode.IDLE: "空闲",
        StatusCode.SCANNING_START: "开始扫描WiFi网络",
        StatusCode.SCANNING_COMPLETE: "WiFi扫描完成",
        StatusCode.CONNECTING: "正在连接WiFi网络",
        StatusCode.CONNECTED: "成功连接到WiFi网络",
        StatusCode.CONNECTION_FAILED: "WiFi连接失败",
        StatusCode.AUTH_SUCCESS: "WiFi认证成功",
        StatusCode.AUTH_FAILED: "WiFi密码错误",
        StatusCode.IP_OBTAINED: "获取IP地址成功",
        StatusCode.DHCP_FAILED: "DHCP获取失败",
        StatusCode.NETWORK_READY: "网络就绪",
        StatusCode.NETWORK_UNREACHABLE: "网络不可达",
        StatusCode.SIGNAL_WEAK: "WiFi信号太弱",
        StatusCode.TIMEOUT: "操作超时",
        StatusCode.INVALID_CONFIG: "配置无效"
    },
    "en_US": {
        StatusCode.IDLE: "Idle",
        StatusCode.SCANNING_START: "Starting WiFi scan",
        StatusCode.SCANNING_COMPLETE: "WiFi scan completed",
        StatusCode.CONNECTING: "Connecting to WiFi",
        StatusCode.CONNECTED: "Successfully connected to WiFi",
        StatusCode.CONNECTION_FAILED: "WiFi connection failed",
        StatusCode.AUTH_SUCCESS: "WiFi authentication successful",
        StatusCode.AUTH_FAILED: "WiFi password incorrect",
        StatusCode.IP_OBTAINED: "IP address obtained",
        StatusCode.DHCP_FAILED: "DHCP failed",
        StatusCode.NETWORK_READY: "Network ready",
        StatusCode.NETWORK_UNREACHABLE: "Network unreachable",
        StatusCode.SIGNAL_WEAK: "WiFi signal too weak",
        StatusCode.TIMEOUT: "Operation timeout",
        StatusCode.INVALID_CONFIG: "Invalid configuration"
    }
}

# Error solutions
ERROR_SOLUTIONS = {
    StatusCode.AUTH_FAILED: {
        "zh_CN": "请检查WiFi密码是否正确",
        "en_US": "Please check if the WiFi password is correct"
    },
    StatusCode.SIGNAL_WEAK: {
        "zh_CN": "请靠近路由器或选择信号更强的网络",
        "en_US": "Please move closer to the router or choose a network with stronger signal"
    },
    StatusCode.DHCP_FAILED: {
        "zh_CN": "请检查路由器DHCP设置或尝试手动配置IP",
        "en_US": "Please check router DHCP settings or try manual IP configuration"
    },
    StatusCode.NETWORK_UNREACHABLE: {
        "zh_CN": "请检查网络连接和路由器状态",
        "en_US": "Please check network connection and router status"
    },
    StatusCode.TIMEOUT: {
        "zh_CN": "操作超时，请重试",
        "en_US": "Operation timed out, please retry"
    }
}


@dataclass
class Notification:
    """Notification data structure."""
    
    timestamp: float
    status_code: int
    status_name: str
    message: str
    details: Optional[Dict[str, Any]] = None
    error: Optional[str] = None
    solution: Optional[str] = None
    
    def to_json(self) -> str:
        """Convert notification to JSON string."""
        data = {
            "timestamp": self.timestamp,
            "status_code": f"0x{self.status_code:02X}",
            "status_name": self.status_name,
            "message": self.message
        }
        
        if self.details:
            data["details"] = self.details
            
        if self.error:
            data["error"] = self.error
            
        if self.solution:
            data["solution"] = self.solution
            
        return json.dumps(data, ensure_ascii=False)
    
    def to_bytes(self) -> bytes:
        """Convert notification to bytes for BLE transmission."""
        return self.to_json().encode('utf-8')


class SubscriptionManager:
    """Manage notification subscriptions."""
    
    def __init__(self, max_subscribers: int = 5):
        """
        Initialize subscription manager.
        
        Args:
            max_subscribers: Maximum number of concurrent subscribers
        """
        self.max_subscribers = max_subscribers
        self.subscribers: Set[str] = set()
        self.lock = threading.Lock()
        
    def subscribe(self, device_path: str) -> bool:
        """
        Subscribe a device to notifications.
        
        Args:
            device_path: DBus path of the device
            
        Returns:
            True if subscription successful, False if limit reached
        """
        with self.lock:
            if len(self.subscribers) >= self.max_subscribers:
                logger.warning(f"Subscription limit reached ({self.max_subscribers})")
                return False
                
            self.subscribers.add(device_path)
            logger.info(f"Device subscribed: {device_path}")
            return True
            
    def unsubscribe(self, device_path: str):
        """
        Unsubscribe a device from notifications.
        
        Args:
            device_path: DBus path of the device
        """
        with self.lock:
            if device_path in self.subscribers:
                self.subscribers.remove(device_path)
                logger.info(f"Device unsubscribed: {device_path}")
                
    def get_subscribers(self) -> List[str]:
        """Get list of current subscribers."""
        with self.lock:
            return list(self.subscribers)
            
    def is_subscribed(self, device_path: str) -> bool:
        """Check if a device is subscribed."""
        with self.lock:
            return device_path in self.subscribers
            
    def clear(self):
        """Clear all subscriptions."""
        with self.lock:
            self.subscribers.clear()
            logger.info("All subscriptions cleared")


class NotificationBatcher:
    """Batch multiple notifications for efficient transmission."""
    
    def __init__(self, batch_window_ms: int = 100, max_batch_size: int = 3):
        """
        Initialize notification batcher.
        
        Args:
            batch_window_ms: Time window for batching in milliseconds
            max_batch_size: Maximum notifications per batch
        """
        self.batch_window_ms = batch_window_ms
        self.max_batch_size = max_batch_size
        self.queue: Queue[Notification] = Queue()
        self.timer: Optional[threading.Timer] = None
        self.lock = threading.Lock()
        self.batch_callback = None
        
    def add(self, notification: Notification):
        """
        Add a notification to the batch queue.
        
        Args:
            notification: Notification to add
        """
        self.queue.put(notification)
        
        with self.lock:
            if self.timer is None or not self.timer.is_alive():
                # Start batch timer
                self.timer = threading.Timer(
                    self.batch_window_ms / 1000.0,
                    self._flush_batch
                )
                self.timer.start()
                
            # Check if batch is full
            if self.queue.qsize() >= self.max_batch_size:
                self._flush_batch()
                
    def _flush_batch(self):
        """Flush the current batch."""
        notifications = []
        
        try:
            while not self.queue.empty() and len(notifications) < self.max_batch_size:
                notifications.append(self.queue.get_nowait())
        except Empty:
            pass
            
        if notifications and self.batch_callback:
            self.batch_callback(notifications)
            
        with self.lock:
            if self.timer:
                self.timer.cancel()
                self.timer = None
                
    def set_callback(self, callback):
        """Set the callback for batch delivery."""
        self.batch_callback = callback
        
    def stop(self):
        """Stop the batcher and flush remaining notifications."""
        with self.lock:
            if self.timer:
                self.timer.cancel()
                self.timer = None
                
        self._flush_batch()


class StatusNotificationManager:
    """Manage status notifications for BLE clients."""
    
    def __init__(self, language: str = "zh_CN"):
        """
        Initialize notification manager.
        
        Args:
            language: Default language for messages
        """
        self.language = language
        self.subscription_manager = SubscriptionManager()
        self.batcher = NotificationBatcher()
        self.batcher.set_callback(self._send_batch)
        self.notification_callback = None
        self.error_history: List[Notification] = []
        self.max_error_history = 100
        
        # Initialize audit logging
        if HAS_SYSLOG:
            syslog.openlog("cloud-printer-ble", syslog.LOG_PID, syslog.LOG_DAEMON)
        
    def set_notification_callback(self, callback):
        """
        Set the callback for sending notifications.
        
        Args:
            callback: Function to call with notification data
        """
        self.notification_callback = callback
        
    def register_subscriber(self, device_path: str) -> bool:
        """
        Register a device for notifications.
        
        Args:
            device_path: DBus path of the device
            
        Returns:
            True if registration successful
        """
        return self.subscription_manager.subscribe(device_path)
        
    def unregister_subscriber(self, device_path: str):
        """
        Unregister a device from notifications.
        
        Args:
            device_path: DBus path of the device
        """
        self.subscription_manager.unsubscribe(device_path)
        
    def send_notification(
        self,
        status_code: StatusCode,
        details: Optional[Dict[str, Any]] = None,
        language: Optional[str] = None
    ):
        """
        Send a status notification.
        
        Args:
            status_code: Status code to send
            details: Additional details
            language: Language for message (uses default if None)
        """
        lang = language or self.language
        
        # Create notification
        notification = self._create_notification(status_code, details, lang)
        
        # Add to error history if it's an error
        if self._is_error_status(status_code):
            self._add_to_error_history(notification)
            
        # Log permission use for audit
        if status_code in [StatusCode.SCANNING_START, StatusCode.CONNECTING]:
            self._log_permission_use(f"NetworkManager action for {status_code.name}")
            
        # Add to batch queue
        self.batcher.add(notification)
        
    def send_immediate(
        self,
        status_code: StatusCode,
        details: Optional[Dict[str, Any]] = None,
        language: Optional[str] = None
    ):
        """
        Send a notification immediately without batching.
        
        Args:
            status_code: Status code to send
            details: Additional details
            language: Language for message
        """
        lang = language or self.language
        notification = self._create_notification(status_code, details, lang)
        
        if self._is_error_status(status_code):
            self._add_to_error_history(notification)
            
        self._send_single(notification)
        
    def _create_notification(
        self,
        status_code: StatusCode,
        details: Optional[Dict[str, Any]],
        language: str
    ) -> Notification:
        """Create a notification object."""
        message = MESSAGES.get(language, MESSAGES["en_US"]).get(
            status_code,
            f"Unknown status: {status_code}"
        )
        
        error = None
        solution = None
        
        if self._is_error_status(status_code):
            error = message
            if status_code in ERROR_SOLUTIONS:
                solution = ERROR_SOLUTIONS[status_code].get(
                    language,
                    ERROR_SOLUTIONS[status_code]["en_US"]
                )
                
        return Notification(
            timestamp=time.time(),
            status_code=status_code,
            status_name=status_code.name,
            message=message,
            details=details,
            error=error,
            solution=solution
        )
        
    def _is_error_status(self, status_code: StatusCode) -> bool:
        """Check if status code represents an error."""
        error_codes = [
            StatusCode.CONNECTION_FAILED,
            StatusCode.AUTH_FAILED,
            StatusCode.DHCP_FAILED,
            StatusCode.NETWORK_UNREACHABLE,
            StatusCode.SIGNAL_WEAK,
            StatusCode.TIMEOUT,
            StatusCode.INVALID_CONFIG
        ]
        return status_code in error_codes
        
    def _add_to_error_history(self, notification: Notification):
        """Add notification to error history."""
        self.error_history.append(notification)
        
        # Trim history if too large
        if len(self.error_history) > self.max_error_history:
            self.error_history = self.error_history[-self.max_error_history:]
            
    def _send_batch(self, notifications: List[Notification]):
        """Send a batch of notifications."""
        if not self.notification_callback:
            logger.warning("No notification callback set")
            return
            
        # Combine notifications into a single message
        if len(notifications) == 1:
            # Single notification, send as-is
            self._send_single(notifications[0])
        else:
            # Multiple notifications, create batch message
            batch_data = {
                "type": "batch",
                "count": len(notifications),
                "notifications": [
                    json.loads(n.to_json()) for n in notifications
                ]
            }
            
            batch_json = json.dumps(batch_data, ensure_ascii=False)
            
            # Send to all subscribers
            for device in self.subscription_manager.get_subscribers():
                try:
                    self.notification_callback(batch_json.encode('utf-8'), device)
                except Exception as e:
                    logger.error(f"Failed to send batch to {device}: {e}")
                    
    def _send_single(self, notification: Notification):
        """Send a single notification."""
        if not self.notification_callback:
            logger.warning("No notification callback set")
            return
            
        data = notification.to_bytes()
        
        # Send to all subscribers
        for device in self.subscription_manager.get_subscribers():
            try:
                self.notification_callback(data, device)
            except Exception as e:
                logger.error(f"Failed to send notification to {device}: {e}")
                
    def _log_permission_use(self, action: str, result: str = "SUCCESS"):
        """
        Log permission use for audit.
        
        Args:
            action: Action performed
            result: Result of the action
        """
        if HAS_SYSLOG:
            syslog.syslog(
                syslog.LOG_AUTH | syslog.LOG_INFO,
                f"Permission {action}: {result}"
            )
        else:
            logger.info(f"AUDIT: Permission {action}: {result}")
        
    def get_error_history(self, limit: int = 10) -> List[Dict[str, Any]]:
        """
        Get recent error history.
        
        Args:
            limit: Maximum number of errors to return
            
        Returns:
            List of error notifications as dictionaries
        """
        recent_errors = self.error_history[-limit:]
        return [json.loads(error.to_json()) for error in recent_errors]
        
    def clear_error_history(self):
        """Clear error history."""
        self.error_history.clear()
        
    def stop(self):
        """Stop the notification manager."""
        self.batcher.stop()
        self.subscription_manager.clear()
        if HAS_SYSLOG:
            syslog.closelog()